/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement the API of class symbol table.
 *
 *
 * @author  WXQ#XDU
 * @date    2024.12
 */


#include <stdlib.h>

#include "inside/_stb_inner.h"


/** initialize inner data of this module */
int symtable_module_init() {
    // do nothing now
    return 0;
}

/** clean global data of this module */
void symtable_module_clean() {
    // do nothing now
}

static void symtable_destroy_impl (t_symtable_ptr pThis) {
    if (NULL == pThis)
        return;

    hashmap_destroy(pThis->localNames );
    list_clean ( &(pThis->blockTables) );

    if ( LOG_DEBUG_ENABLE() ) {
        t_tree_ptr  treeNode = pThis->ownerTreeNode;
        bool isLeaf = tree_is_leaf(pThis->ownerTreeNode);
        if (isLeaf) treeNode = tree_get_parent(treeNode);
        t_position posStart = tree_get_startPosition(treeNode);
        t_position posStop  = tree_get_stopPosition(treeNode);
        log_debug("DESTROY-ed  symTABLE %tx of %s%s%s for input [%d:%d , %d:%d] ",
                  pThis, isLeaf ? "function " : "scope NT-",
                  tree_get_markText(pThis->ownerTreeNode),
                  isLeaf ? "()" : "{}",
                  posStart.line, posStart.column,
                  posStop.line, posStop.column  );
    }

    free (pThis);
}

// Callback function to destroy sub-block's table
static void callback_freeBlockTable (t_list_app_value pTable) {
    t_symtable_ptr symtablePtr = (t_symtable_ptr)pTable;
    symtable_destroy_impl (symtablePtr);
}


// creates one symbol table
t_symtable_ptr symtable_new (t_tree_ptr  ownerTreeNode,
                             t_symtable_ptr parentPtr)
{
    t_symtable_ptr pThis = (t_symtable_ptr)calloc(1, sizeof(t_symtable));
    if (NULL == pThis) {
        ERROR_REPORT_MALLOC();
    } else {
        pThis->ownerTreeNode   = ownerTreeNode;
        pThis->parent = parentPtr;
        list_init (&(pThis->blockTables), callback_freeBlockTable);
        pThis->localNames = NULL; // creates it when the first name's
                                  // definition is scanned in this scope
        if ( LOG_DEBUG_ENABLE() ) {
            t_tree_ptr  treeNode = pThis->ownerTreeNode;
            bool isLeaf = tree_is_leaf(pThis->ownerTreeNode);
            if (isLeaf) treeNode = tree_get_parent(treeNode);
            t_position posStart = tree_get_startPosition(treeNode);
            t_position posStop  = tree_get_stopPosition(treeNode);
            log_debug("CREATED  symTABLE %tx of %s%s%s for input [%d:%d , %d:%d]",
                      pThis,
                      isLeaf ? "function \"" : "scope NT-",
                      tree_get_markText(pThis->ownerTreeNode),
                      isLeaf ? "\"" : "",
                      posStart.line, posStart.column,
                      posStop.line, posStop.column );
        }
    }

    return pThis;
}


// destroy a symbol tables and its children for enclosed scope
void symtable_destroy (t_symtable_ptr pThis) {
    log_trace("DESTROY-ing symTABLE Tree with root %tx ...", pThis);
    symtable_destroy_impl(pThis);
    log_debug("DESTROY-ed  symTABLE Tree with root %tx", pThis);
}


// Calculate the hash code for a tree node marked with ID token.
// This ID is the Key for a symbol entry
static t_hashcode mapfunc_keyhash (t_map_key keyNode) {
    t_tree_ptr definitionTreeNode = (t_tree_ptr)keyNode;
    t_terminal_ptr terminalPtr =  tree_get_terminal(definitionTreeNode);
    if (NULL == terminalPtr) return 0;
    const char * name = token_get_text( terminalPtr->tokenPtr );
    return amcc_hashcode(name);
}

// compare keys of two tree nodes marked with ID token.
// This ID is the real Key for a symbol entry
static int mapfunc_keycomp (t_map_key K1, t_map_key K2) {
    if      (  K1  == K2 )  return 0;
    else if ( NULL == K1 )  return 1;
    else if ( NULL == K2 )  return -1;

    t_terminal_ptr terminalPtr1 =  tree_get_terminal((t_tree_ptr)K1);
    t_terminal_ptr terminalPtr2 =  tree_get_terminal((t_tree_ptr)K2);

    const char * name1 = token_get_text( terminalPtr1->tokenPtr );
    const char * name2 = token_get_text( terminalPtr2->tokenPtr );
    return strcmp(name1, name2);
}

// destroy mapped t_symentry_ptr value
static void mapfunc_free (t_map_key K, t_map_value V) {
    t_symentry_ptr symentryPtr = (t_symentry_ptr)V;

    if (tree_is_funcNameDefinition(symentryPtr->definitionTreeNode)) {
        t_func_symInfo_ptr funcInfo = symentryPtr->content.funcInfo;
        list_clean( &(funcInfo->parameterList) );
        symtable_destroy_impl(funcInfo->symTablePtr);

        // NOTICE: Don't type_destroy(this func type).
        //         It is the work of the type pool.

    } else if (tree_is_varNameDefinition(symentryPtr->definitionTreeNode)) {
        list_destroy(symentryPtr->content.varInfo->initExprs);

        // NOTICE: Don't type_destroy(this var type)
        //         It is the work of the type pool.

    } else if (tree_is_labelNameDefinition(symentryPtr->definitionTreeNode)) {
        // do nothing
    }

    free(symentryPtr);
}


int  symtable_get_count (t_symtable_ptr pThis) {
    if (NULL == pThis || NULL == pThis->localNames) return 0;
    return hashmap_size( pThis->localNames );
}

size_t  symtable_get_memsize (t_symtable_ptr pThis) {
    return (NULL == pThis) ? 0 : pThis->totalSizeOfVariables;
}


t_symentry_ptr symtable_find_local (t_symtable_ptr pThis, t_tree_ptr idNode)
{
    if ( NULL == pThis || NULL == idNode )
        return NULL;
    return hashmap_find(pThis->localNames, idNode);
}


t_symentry_ptr symtable_find (t_symtable_ptr pThis, t_tree_ptr idNode) {
    if ( NULL == pThis || NULL == idNode )
        return NULL;
    t_symentry_ptr result    = NULL;
    for ( t_symtable_ptr pSymTable = pThis    ;
          NULL == result && NULL != pSymTable ; )
    {
        result = hashmap_find(pSymTable->localNames, idNode);
        pSymTable = pSymTable->parent;
    }

    return result;
}


#define newLocalNameSet()  hashmap_new(0, mapfunc_keyhash, \
                            mapfunc_keycomp, mapfunc_free)

t_symentry_ptr symtable_putVarDefine (t_symtable_ptr pThis,
                                      t_type_ptr     varType,
                                      t_tree_ptr     definitionNode,
                                      size_t         memory_offset,
                                      t_tree_ptr     initializerNode )
{
    t_symentry_ptr pSymEntry = (t_symentry_ptr)calloc(1, sizeof(*pSymEntry));
    if (NULL == pSymEntry){
        ERROR_REPORT_MALLOC();
        return NULL;
    }

    t_var_symInfo_ptr varInfor = (t_var_symInfo_ptr)calloc(1, sizeof(*varInfor));
    if (NULL == varInfor){
        free (pSymEntry);
        ERROR_REPORT_MALLOC();
        return NULL;
    }

    varInfor->typePtr               = varType;
    varInfor->offset                = memory_offset;
    varInfor->initializerNode       = initializerNode;
    pSymEntry->content.varInfo      = varInfor;

    // set a bi-direction link
    pSymEntry->definitionTreeNode   = definitionNode;
    tree_set_symbolEntry(definitionNode, pSymEntry);

    // creates the underlying table on need
    if (NULL == pThis->localNames) {
        pThis->localNames = newLocalNameSet();
        if (NULL == pThis->localNames) {
            mapfunc_free(NULL, pSymEntry);
            ERROR_REPORT_MALLOC();
            return NULL;
        }
    }

    // put the symbol entry into underlying table
    t_mapresult_insert result = hashmap_put(pThis->localNames,
                                            definitionNode,
                                            pSymEntry);
    t_position pos = tree_get_startPosition(definitionNode);
    const char * thisName = tree_get_markText(definitionNode);
    if ( LOG_TRACE_ENABLE() ) {
        log_trace("hashmap_put returns [isSuccess=%s, value=%tx], var=%s, pos=%d:%d",
                  result.success ? "true" : "false", result.value,
                  thisName, pos.line, pos.column );
    }

    if (false == result.success) {
        mapfunc_free(NULL, pSymEntry);
        if ( NULL != result.value ) {
            t_symentry_ptr prevDefEntry = (t_symentry_ptr)result.value;
            t_position     prevDefPos = tree_get_startPosition(prevDefEntry->definitionTreeNode);
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_REDEFINE, "variable", thisName,
                               prevDefPos.line, prevDefPos.column, prevDefPos.filePath);
        }
        //return NULL;  // this may cause other program to CRASH
        tree_set_symbolEntry(definitionNode, result.value);
        log_warn("On name-redefine, return old symENTRY %tx of previous definition", result.value);
        return result.value;
    }

    return pSymEntry;
}



t_symentry_ptr symtable_putFuncDefine (t_symtable_ptr pThis,
                                       t_tree_ptr     definitionNode,
                                       t_func_symInfo_ptr funcSymInfo)
{
    t_symentry_ptr pSymEntry = (t_symentry_ptr)calloc(1, sizeof(*pSymEntry));
    if (NULL == pSymEntry) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }

    pSymEntry->content.funcInfo   = funcSymInfo;

    // set a bi-direction link
    pSymEntry->definitionTreeNode = definitionNode;
    tree_set_symbolEntry(definitionNode, pSymEntry);

    // creates the underlying table on need
    if (NULL == pThis->localNames) {
        pThis->localNames = newLocalNameSet();
        if (NULL == pThis->localNames) {
            mapfunc_free(NULL, pSymEntry);
            ERROR_REPORT_MALLOC();
            return NULL;
        }
    }

    // put the symbol entry into underlying table
    t_mapresult_insert result = hashmap_put(pThis->localNames,
                                            definitionNode, pSymEntry);
    t_position pos = tree_get_startPosition(definitionNode);
    const char * thisName = token_get_text( tree_get_token(definitionNode) );
    if ( LOG_TRACE_ENABLE() ) {
        log_trace("hashmap_put returns [isSuccess=%s, value=%tx], function=%s, pos=%d:%d",
                  result.success ? "true" : "false", result.value,
                  thisName, pos.line, pos.column );
    }

    if (false == result.success) {
        mapfunc_free(NULL, pSymEntry);
        if ( NULL != result.value ) {
            t_symentry_ptr prevDefEntry = (t_symentry_ptr)result.value;
            t_position     prevDefPos = tree_get_startPosition(prevDefEntry->definitionTreeNode);
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_REDEFINE, "function", thisName,
                               prevDefPos.line, prevDefPos.column, prevDefPos.filePath);
        }
        //return NULL;  // this may cause other program to CRASH
        tree_set_symbolEntry(definitionNode, result.value);
        log_warn("On name-redefine, return old symENTRY %tx of previous definition", result.value);
        return result.value;
    }

    return pSymEntry;
}



t_symentry_ptr symtable_putLabelDefine (t_symtable_ptr pThis,
                                        t_tree_ptr definitionNode)
{
    t_symentry_ptr pSymEntry = (t_symentry_ptr)calloc(1, sizeof(*pSymEntry));
    if (NULL == pSymEntry) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }

    t_label_symInfo_ptr lblInfo = (t_label_symInfo_ptr)calloc(1, sizeof(*lblInfo));
    if (NULL == lblInfo) {
        free (pSymEntry);
        ERROR_REPORT_MALLOC();
        return NULL;
    }
    lblInfo->labeledStatementNode   = tree_get_parent(definitionNode);
    pSymEntry->content.labelInfo = lblInfo;

    // set a bi-direction link
    pSymEntry->definitionTreeNode  = definitionNode;
    tree_set_symbolEntry(definitionNode, pSymEntry);

    // creates the underlying table on need
    if (NULL == pThis->localNames) {
        pThis->localNames = newLocalNameSet();
        if (NULL == pThis->localNames) {
            mapfunc_free(NULL, pSymEntry);
            ERROR_REPORT_MALLOC();
            return NULL;
        }
    }

    // put the symbol entry into underlying table
    t_mapresult_insert result = hashmap_put(pThis->localNames,
                                            definitionNode, pSymEntry);
    t_position pos = tree_get_startPosition(definitionNode);
    const char * thisName = token_get_text( tree_get_token(definitionNode) );
    if (LOG_TRACE_ENABLE() ) {
        log_trace("hashmap_put returns [isSuccess=%s, value=%tx], label=%s, pos=%d:%d",
                  result.success ? "true" : "false", result.value,
                  thisName, pos.line, pos.column );
    }

    if ( false == result.success ) {
        mapfunc_free(NULL, pSymEntry);
        if ( NULL != result.value ) {
            t_symentry_ptr prevDefEntry = (t_symentry_ptr)result.value;
            t_position     prevDefPos = tree_get_startPosition(prevDefEntry->definitionTreeNode);
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_REDEFINE, "label", thisName,
                               prevDefPos.line, prevDefPos.column, prevDefPos.filePath);
        }
        //return NULL;  // this may cause other program to CRASH
        tree_set_symbolEntry(definitionNode, result.value);
        log_warn("On name-redefine, return old symENTRY %tx of previous definition", result.value);
        return result.value;
    }

    return pSymEntry;
}

extern t_tree_ptr  tree_new_pseudo (const char * txt, EnumTokenKind kind);
t_symentry_ptr symtable_find_name (t_symtable_ptr pThis, const char * name) {
    t_tree_ptr pseudoNode = tree_new_pseudo(name, TK_ID);
    t_symentry_ptr  sym = symtable_find(pThis, pseudoNode);
    tree_destroy(pseudoNode);
    return sym;
}
